/**
 * 
 */
package pl.koziolekweb.pmt;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

/**
 * 
 * <p>
 * Klasa służy do uruchamiania metod prywatnych i ustawiania pól prywatnych
 * obiektów w trakcie testów jednostkowych.
 * </p>
 * <p>
 * <b>EN:</b> Class can run private methods, set private fields for object in
 * unt tests.
 * </p>
 * 
 * @author bartlomiejk
 * 
 */
public class PrivateMemberTester {

	/**
	 * <p>
	 * Uruchamia wskazaną metodę. Metoda może być prywatna.
	 * </p>
	 * <p>
	 * <b>EN:</b> Run method. Method can be private.
	 * </p>
	 * 
	 * @param methodName
	 *            nazwa metody uruchamianej. <br/>
	 *            <b>EN:</b> Method name.
	 * @param owner
	 *            obiekt na rzecz, ktorego uruchamiana jest metoda. <br/>
	 *            <b>EN:</b> Object for which method is invoked.
	 * @param args
	 *            argumenty metody. <br/>
	 *            <b>EN:</b> Arguments of method.
	 * @param clazz
	 *            klasa do której należy metoda. <br/>
	 *            <b>EN:</b> Owner class.
	 * @param parameterTypes
	 *            tablica klas argumentów metody. <br/>
	 *            <b>EN:</b> Array of arguments class.
	 * @return to samo co wywoływana metoda. <br/>
	 *         <b>EN:</b> the same as invoked method.
	 * @throws Throwable
	 *             Błąd jaki został zwrocony przez wywolywaną metodę lub błąd
	 *             wywołania metody. <br/>
	 *             <b>EN:</b> Exeption from method or invoker exeption.
	 */
	public static Object runMethod(final String methodName, final Object owner, final Object[] args,
			final Class<?> clazz, final Class<?>... parameterTypes) throws Throwable {
		Object o = null;
		try {
			Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
			method.setAccessible(true);
			o = method.invoke(owner, args);
			return o;
		} catch (Throwable e) {
			Throwable t = null;
			if (e.getCause() != null) {
				t = (Throwable) e.getCause();
			} else {
				t = e;
			}
			throw t;
		}
	}

	/**
	 * <p>
	 * Pobiera wartość danego pola. Pole może być prywatne.
	 * </p>
	 * <p>
	 * <b>EN:</b> Return value of field. Field can be private.
	 * </p>
	 * 
	 * 
	 * @param fieldName
	 *            nazwa pola. <br/>
	 *            <b>EN:</b> Field name.
	 * @param owner
	 *            obiekt z ktorego czerpane są informacje o polu. <br/>
	 *            <b>EN:</b> Object that field will get.
	 * @param ownerClass
	 *            Klasa do której należy pole. <br/>
	 *            <b>EN:</b> Field class.
	 * @return wartość pola jako {@link Object}. <br/>
	 *         <b>EN:</b> field value ad {@link Object}.
	 * @throws NoSuchFieldException
	 *             jeżeli podane pole nie istnieje. <br/>
	 *             <b>EN:</b> If field does not exist.
	 */
	public static Object getPrivateFiled(final String fieldName, final Object owner, final Class<?> ownerClass)
			throws NoSuchFieldException {

		Field declaredField = ownerClass.getDeclaredField(fieldName);
		declaredField.setAccessible(true);
		try {
			return declaredField.get(owner);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * <p>
	 * Metoda pozwala na ustawienie prywatnego pola. Pole nie może być
	 * jednocześnie oznaczone jako <code>static</code> i <code>final</code>. W
	 * takim przypadku metoda kończy działanie.
	 * </p>
	 * <p>
	 * <b>EN:</b> Method allow set value to indicated field. Field can not be
	 * both <code>static</code> and <code>final</code>. In this case method
	 * return.
	 * </p>
	 * 
	 * @param fieldName
	 *            nazwa pola.<br/>
	 *            <b>EN:</b> Field name.
	 * @param owner
	 *            obiekt dla którego chcemy ustawić pole. <br/>
	 *            <b>EN:</b> Object for which field we set.
	 * @param value
	 *            nowa wartość pola. <br/>
	 *            <b>EN:</b> New value of field.
	 * @param ownerClass
	 *            klasa do której należy pole. <br/>
	 *            <b>EN:</b> Field class.
	 * @throws NoSuchFieldException
	 *             jeżeli podane pole nie istnieje. <br/>
	 *             <b>EN:</b> If field does not exist.
	 */
	public static void setPrivateField(final String fieldName, final Object owner, final Object value,
			final Class<?> ownerClass) throws NoSuchFieldException {
		Field declaredField = ownerClass.getDeclaredField(fieldName);
		if (Modifier.isStatic(declaredField.getModifiers())) {
			return;
		}
		declaredField.setAccessible(true);
		try {
			declaredField.set(owner, value);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * <p>
	 * Metoda ustawia wartość wskazanego pola dla wskazanego obiektu na
	 * <code>null</code>.
	 * </p>
	 * <p>
	 * <b>EN:</b> Method set <code>null</code> value for indicated field.
	 * </p>
	 * 
	 * @param fieldName
	 *            nazwa pola. <br/>
	 *            <b>EN:</b> Field name.
	 * @param owner
	 *            obiekt, którego pole ustawiamy. <br/>
	 *            <b>EN:</b> Object which field we set.
	 * @throws NoSuchFieldException
	 *             jeżeli nazwa pola jest nieprawidłowa. <br/>
	 *             <b>EN:</b> If field does not exist.
	 */
	public static void nullField(final String fieldName, final Object owner) throws NoSuchFieldException {
		Field declaredField = owner.getClass().getDeclaredField(fieldName);
		declaredField.setAccessible(true);
		try {
			declaredField.set(owner, null);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * <p>
	 * Metoda ustawia wszystkie podane pola z parametru <code>fields</code> na
	 * zadane w tej mapie wartości. Ustawianie pól mapy odbywa się na podstawie
	 * listy zadeklarowanych pól. Oznacza to, że podanie nieprawidłowej nazwy
	 * pola nie spowoduje błędu.
	 * </p>
	 * <p>
	 * <b>EN:</b> Emthod set all fields from parameter <code>fields</code> to
	 * values set in this map. If field does not exist then method ignore this
	 * record.
	 * </p>
	 * 
	 * @param owner
	 *            obiekt na rzecz ktorego wykonywane jest ustawianie. <br/>
	 *            <b>EN:</b> Object which fields we set.
	 * @param fields
	 *            mapa pól. Kluczem jest wartość pola. <br/>
	 *            <b>EN:</b> map of fields and values. Keys are fields names.
	 */
	public static void setAll(final Object owner, final Map<String, Object> fields) {
		Field[] declaredFields = owner.getClass().getDeclaredFields();
		try {
			for (Field field : declaredFields) {
				Object fieldValue = fields.get(field.getName());
				if (fieldValue != null) {
					setPrivateField(field.getName(), owner, fieldValue, owner.getClass());
				}
			}
		} catch (NoSuchFieldException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * <p>
	 * Metoda podobna do {@link #setAll(Object, Map)}, ale ustawia wszystkie
	 * pola obiektu na <code>null</code>.
	 * </p>
	 * <p>
	 * <b>EN:</b> Set all fields to <code>null</code>.
	 * </p>
	 * 
	 * @param owner
	 *            obiekt, którego pola są ustawiane na <code>null</code>. <br/>
	 *            <b>EN:</b> Object which fields we set.
	 */
	public static void nullAll(final Object owner) {
		Field[] declaredFields = owner.getClass().getDeclaredFields();
		try {
			for (Field field : declaredFields) {
				setPrivateField(field.getName(), owner, null, owner.getClass());
			}
		} catch (NoSuchFieldException e) {
			throw new RuntimeException(e);
		}
	}
}
